﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Text;
using System.Net.Sockets;
using System.Collections;
using System.IO;
using System.Net;
using System.Text;
using System.Net.Sockets;


namespace ClockSync2
{
    public class Master
    {
        NeighbourTable Neighbours;
        RunningClock clock;
        tcpClient[] clients;
        tcpServer[] servers;
        System.Threading.Thread[] serverThreads;
        System.Threading.Thread threadClock;
        int myOffset;
        int threadCount = 0;
        bool first = true;
        Neighbour self;

        public Master(NeighbourTable ntb, RunningClock c)
        {

            self=null;
            Console.WriteLine("starting constructor of master");
            this.Neighbours = ntb;
            startFresh();
            Console.WriteLine("\tcreating new RunningClock");

            this.clock = c;

            foreach (Neighbour n in Neighbours.Neighbours)
            {
                if (n.Address.Equals(utils.FindLanAddress()))
                {
                    self = n;
                }
            }

            //init tcpServer threads (listeners)
            Console.WriteLine("\tcreating new thread array (serverThreads)");
            serverThreads = new System.Threading.Thread[Neighbours.Neighbours.Count - 1]; //-1 because Master also appears in the table

            //populate the 'clients' with IPs and Ports
            Console.WriteLine("\tcreating new client");

            clients = new tcpClient[Neighbours.Neighbours.Count - 1];
            servers = new tcpServer[Neighbours.Neighbours.Count - 1];

            int i = 0;
            Console.WriteLine("\tsatrting foreach of constructor");
            foreach (Neighbour n in Neighbours.Neighbours)
            {
                if (n.IsAlive)
                {
                    if (!n.Address.Equals(utils.FindLanAddress()))
                    {
                        Console.WriteLine("\t\tin if of foreach, creating new tcpClient");

                        try
                        {
                            if (n.IsAlive)
                                clients[i] = new tcpClient(n.Address, n.syncPort + Neighbours.Neighbours.Count);
                        }
                        catch
                        {
                            //i was unable to connect to a certain client, he's presumably MIA
                            n.IsAlive = false;
                        }
                        try
                        {
                            if (n.IsAlive)
                                servers[i] = new tcpServer(IPAddress.Any, n.syncPort, n);
                        }
                        catch
                        {
                            //i was unable to open a port for this client, my ports are fucked up?

                            //i am a bad master, someone else should be master, and i should DIE
                            //throw ElectionsStarted
                            Console.WriteLine("********************************");
                            Console.WriteLine("I AM A BAD MASTER AND CAN'T OPEN PORTS!!!");
                            Console.WriteLine("********************************");

                            Environment.Exit(0);
                        }
                        i++;
                    }
                }
            }
            Console.WriteLine("\tending constructor");
        }


        //implements all of the master's logic
        public void start()
        {


            Console.WriteLine("starting start function\ncalling pollSlaves()");
            //poll slaves


            pollSlaves();

            Console.WriteLine("returned from pollSlaves()\ncalling avarageTheClocks()");
            //average the clock times, ignoring any values it receives far outside the values of the others

            avarageTheClocks();

            Console.WriteLine("returnd from avarageTheClocks()\n calling sendOffsetsToSlaves()");

            //send out the amount (positive or negative) that each slave must adjust its clock
            sendOffsetsToSlaves();
            
            Console.WriteLine("returnd from sendOffsetsToSlaves()");
            Console.WriteLine("ending start");
        }

        private void sendOffsetsToSlaves()
        {
            Console.WriteLine("starting sendOffsetsToSlaves()\n\tstarting first foreach");
            foreach (tcpClient c in clients)
            {
                Console.WriteLine("\tstarting second foreach");
                foreach (Neighbour n in Neighbours.Neighbours)
                {
                    if (n.Address.Equals(c.IP) && n.IsAlive && !n.Address.Equals(utils.FindLanAddress()))
                    {
                        if (c.tcpclnt.Connected)
                        {
                            //c.exit();
                            //c.open(n.Address, n.syncPort);

                            //prints
                            Console.WriteLine("===========================");
                            Console.WriteLine("slaves offset:" + n.offset);
                            Console.WriteLine("===========================");

                            Console.WriteLine("\tsending msg: " + n.offset.ToString() + " to " + c.IP.ToString() + ":" + c.port);
                            try
                            {

                                c.sendMsg(n.offset.ToString());
                            }
                            catch
                            {
                                //unable to send an offset to a slave, he's persumably MIA...
                                n.IsAlive = false;
                            }
                        }
                        break;
                    }
                }
            }
        }

        private void avarageTheClocks()
        {
            Console.WriteLine("starting avarageTheClocks()");
            long sum = 0;
            int countSlaves = Neighbours.Neighbours.Count;

            //update my clock & sum's
            long myClockInsMS = utils.getTimeInMS((utils.DateTimeAsString(clock.CurrentTime).Split(':')));
            
            //start master RTT! (TKURA)
            self.RTToldTime = DateTime.Now;
            
            Console.WriteLine("\tMaster's time before balagan=\t" + utils.DateTimeAsString(clock.CurrentTime));
            foreach (Neighbour n in Neighbours.Neighbours)
            {
                if (n.Address.Equals(utils.FindLanAddress()))
                {
                    n.timeInMS=myClockInsMS;
                }
                
                //calc tot time sum
                if (n.IsAlive)
                {
                    sum += n.timeInMS;
                }
                else
                {
                    //this slave is dead, dont count him
                    countSlaves--;
                }
            }

            Console.WriteLine("\tSum of all neighbours(includng me) =" + sum);
          
            Console.WriteLine("\tmyClockInsMS=\t" + myClockInsMS);


            //calc the avg of all
            
            double currAvg = sum / countSlaves;
            
            Console.WriteLine("\tcurrAvg =\t" + currAvg);
            
            //see who has a fucked up offset from AVG, cutoff=400ms diffrence
            
            Console.WriteLine("\tsee who has a fucked up offset from AVG, cutoff=400ms diffrence");
            int i = 0;
            long newSum = 0;

            foreach (Neighbour n in Neighbours.Neighbours)
            {
                if (n.IsAlive)
                {
                    if (System.Math.Abs((currAvg - n.timeInMS)) < 400)
                    {
                        Console.WriteLine("\tin foreach in if,sum = " + sum + " slave = " + n.Address.ToString());
                        newSum += n.timeInMS;
                        i++;
                    }
                    else
                    {
                        System.Console.WriteLine("slave " + n.Address.ToString() + " was nupa");
                    }
                }
            }

            //THIS IS THE CASE WHERE WE ALL NIPINU OURSELVES!!! TOFIX!!!!
            if (newSum == 0)
            {
                newSum = myClockInsMS;
                i=1;
            }

            Console.WriteLine("\tnewSum=\t" + newSum);

            //re-avg
            double goodAvg = newSum / i;
            
            Console.WriteLine("\tgoodAvg=\t" + goodAvg);
            
            //update MY neighbour table with their offsets
            foreach (Neighbour n in Neighbours.Neighbours)
            {
                if (n.IsAlive)
                {
                    n.offset = (int)((long)goodAvg - n.timeInMS);
                    Console.WriteLine("\tslave " + n.Address.ToString() + " time should be: " + utils.TimeInMSAsString(n.timeInMS + n.offset));
                }

                //fix my (masters) time with the AVG
                if (n.Address.Equals(utils.FindLanAddress()))
                {
                    self.RTTnewTime = DateTime.Now;
                    self.RTT = (int)utils.subDateTimesGetTotMS(self.RTTnewTime, self.RTToldTime); //tkura

                    Console.WriteLine("TKURA OF MASKER WAS: " + self.RTT);

                    Console.WriteLine("\tgoodTime's TimeInMSAsString=\t" + utils.TimeInMSAsString((long)goodAvg));
                    clock.CurrentTime = utils.StringAsDateTime( utils.TimeInMSAsString((long)goodAvg+self.RTT)  ,DateTime.Now);
                    Console.WriteLine("\tMaster's time after set=\t" + utils.DateTimeAsString(clock.CurrentTime));
                }
            }

        }


        //updates the Neighbour's RTT and Time values
        public void pollSlaves()
        {
            Console.WriteLine("starting pollSlaves()");
            //send the messages
            int j = 0;

            foreach (tcpClient c in clients)
            {
                foreach (Neighbour n in Neighbours.Neighbours)
                {
                    if (n.Address.Equals(c.IP))
                    {

                        if (n.IsAlive)
                        {
                            if (!n.Address.Equals(utils.FindLanAddress()))
                            {
                                Console.WriteLine("\tsending to " + c.IP + " msg: \"!!!\"");

                                try
                                {
                                    c.sendMsg("!!!"); //"secret" message to poll times from slaves
                                }
                                catch
                                {
                                    //was unable to send msg, slave is MIA
                                    n.IsAlive = false;
                                    Console.WriteLine("\t" + n.Address.ToString() + " has died, and now not alive");
                                    break;
                                }

                                if (n.IsAlive)
                                {
                                    Console.WriteLine("\tstarting RTT calculation...");
                                    n.RTToldTime = DateTime.Now;
                                }
                            }

                        }
                        break;
                    }
                }
            }
            
            //listen for results
            int i = 0;
            Console.WriteLine("\tlistening for results");
            foreach (Neighbour n in Neighbours.Neighbours)
            {
                if (n.IsAlive)
                {
                    if (!n.Address.ToString().Equals(utils.FindLanAddress().ToString()))
                    {
                        serverThreads[i] = new System.Threading.Thread(() => listen(n));
                        //serverThreads[i].IsBackground = true;
                        serverThreads[i].Start();
                        i++;
                    }
                }
            }

            //now loop until all threads returned

            Console.WriteLine("\tloop until all threads returned");
            bool flag=true;
            while (flag)
            {
                flag = false;
                foreach (System.Threading.Thread t in serverThreads)
                {
                    if (t.IsAlive)
                    {
                        flag = true;
                        break;
                    }
                }
            }
            Console.WriteLine("\tAll listening threads have returened");
            Console.WriteLine("\tending pollSlaves()");
        }

        public void listen(Neighbour n)
        {
            string fileName = "thread " + n.Address.ToString() + threadCount;
            string text = "";

            text += "starting listen()\n";
            tcpServer ser = null;
            Neighbour tempNeighbour=null;
            byte[] msg = null;
            foreach (Neighbour ne in Neighbours.Neighbours)
            {
                if (n.IsAlive)
                {
                    foreach (tcpServer s in servers)
                    {
                        if (s.ident.Address.Equals(n.Address))
                        {
                            tempNeighbour = n;
                            ser = s;
                        }
                    }
                }
            }

            try
            {
                msg = ser.getMsg();
                if (msg != null && msg.Length != 0)
                    n.RTTnewTime = DateTime.Now;
                else
                {
                    tempNeighbour.IsAlive = false;
                    return;
                }


            }
            catch
            {
               //text += "no server existing for client: " + n.Address + "\n";
                tempNeighbour.IsAlive = false;
            }

            text += "\n\tgot slave's time: " + System.Text.Encoding.UTF8.GetString(msg) + "\n";


            if (tempNeighbour.IsAlive)
            {
                n.RTT = (int)utils.subDateTimesGetTotMS(n.RTTnewTime, n.RTToldTime);

                text += "\nSlaves RTT was:" + n.RTT + "\n";

                text += "\treseting stop-watch\n";

                text += "\t" + n.Address + "`s RTT is " + n.RTT + "\n";

                long hisTimeInMS;
                string str = utils.ByteToStrArray(msg);
                try
                {

                    hisTimeInMS = utils.getTimeInMS((utils.DateTimeAsString(utils.StringAsDateTime(str, clock.CurrentTime)).Split(':')));

                    text += "\thisTimeInMS " + hisTimeInMS + "\n";

                    n.timeInMS = hisTimeInMS;

                    //deduct 0.5*RTT from his time
                    n.timeInMS -= n.RTT / 2;

                    n.time = utils.StringAsDateTime(str, clock.CurrentTime);

                    text += "\t" + n.Address + "s old time is " + n.time + "\n";

                    text += "RTT startTime was: " + utils.DateTimeAsString(n.RTToldTime) + "\nRTT endTime was: " + utils.DateTimeAsString(n.RTTnewTime) + "\n";

                    //System.IO.File.WriteAllText(@"C:\Users\ASoliD\Desktop\" + fileName, text);
                    threadCount++;
                }
                catch
                {
                    n.IsAlive = false;
                    return;
                }
            }
        }


        private void startFresh()
        {
            foreach (Neighbour ne in Neighbours.Neighbours)
            {
                ne.IsAlive = true;
            }
        }

    }
}
